Lists and Listers
Works of Interactive Fiction often need to display lists. Typically these may be lists of objects visible in a room or a container, or a list of items held in the player character's inventory. There are generally two stages involved in displaying a list:
- Generating the list of items (usually objects) that need to be displayed.
- Formatting the list and displaying it on the screen. This stage may also include further filtering of the list.
The first stage is generally carried out by a method such as listContents() or listSubcontentsOf(), defined on Thing, or else a method define on the appropriate Action object, such as the execAction() method of the Inventory action. More will be said about such methods below. The second stage is carried out by a Lister, and it is Listers that we shall look at next.
Listers
The purpose of a Lister is to format and display a list. The various types of lister all descend from the Lister class, which defines the following methods:
- show(lst, paraCnt, paraBrk = true): Show a list of objects. lst is the list of objects to show; paraCnt is the number of paragraph-style descriptions that we've already shown as part of this description. Note, however, that many specific listers replace the paraCnt parameter with a more useful parent parameter, containing the identity of the object whose contents are being listed. If the optional paraBrk parameter is supplied it determines whether or not a paragraph break is added at the end of the list (if this parameter is not specified it defaults to true). The actual work of formatting and displaying the list of items (as opposed to the prefix and suffix text surrounding it) is carried out by a showList() method defined in the language-specific part of the library.
- showListPrefix(lst, pl, paraCnt): Display the list prefix, that is the text that introduces a list (e.g. "In the oven you can see "). The pl parameter defines whether the list should be treated as singular or plural (so that any verb used can agree in number, e.g. "In the oven is/are "). many specific listers replace the paraCnt parameter with a more useful parent parameter, containing the identity of the object whose contents are being listed.
- showListSuffix(lst, pl, paraCnt): Display the list suffix, that is the text that concludes a list (often that may simply be a full-stop/period, but it may be a longer piece of text if the list is displayed as a sentence with the list itself as the subject, e.g. "A ball, a pencil and a diamond are in the blue box. " where the suffix would be " are in the blue box. ")
- showListEmpty(paraCnt): The text to display when the list of items to be displayed is empty (e.g. "The oven is empty. " or "You see nothing of interest in the red box. ")
- listed(obj): Determine whether obj should be listed in this list. Returns true if so, nil if not. By default, we list any object whose listed property is true.
- listOrder(obj): Get an item's sorting order. This returns an integer that gives the relative position in the list; we order the list in ascending order of this value. By default, we return the listOrder property of the object.
- buildList(lst): Returns a string containing what this lister would display, minus the terminating paragraph break.
From this you can able to see that the show() and buildList() methods are for use with an existing Lister (to make it display a list, or else return the list it would display in a single-quoted string), while all the rest are used when defining a Lister.
ItemLister
Many of the Listers actually used in a game are based on a subclass of Lister called ItemLister. This is base class of all the Listers used to list physical objects in a game. Several of the features of this Lister are defined in the language-specific part of the library, but the principal way in which an ItemLister customizes the base Lister class are as follows:
- show(lst, parent, paraBrk = true): On the ItemLister class the paraCnt parameter of the show() method is replaced with a more useful parent parameter, which refers to the items whose contents are being listed. On ItemLister this method also marks every item in the list as mentioned (so that it won't get mentioned again in the same list) and notes that and where the player character has seen it.
- contentsListedProp: This property should contain a property pointer (the default value is &contentsListed) that determines whether an item listed by this lister should have its contents listed in turn. Note that is it actually the Thing method listSubcontentsOf() that makes use of this property when it is constructing a list of items to pass to a lister.
- listRecursively: flag, should the contents of items listed by this lister also be listed? This is true by default except on the openingContentsLister, where it is nil, to suppress the (arguably intrusive) recursive listing of contents of contents when an openable container is opened.
The language-specific part of the library in english.t defines the following additional methods on ItemLister:
- showList(lst, pl, parent): This method actually displays the body of the list (e.g. "a ball, a pencil and a diamond"). It is used by the showList() method and in turn (by default) calls the andList() function (see below) to format the list.
- listName(o): This method is used to define how the name of an individual item should be shown in the list (e.g. "a ball"). This is more complex than it seems since this method is also responsible for displaying further status-related information about in item, such as '(being worn)' or a list of the item's contents (e.g. "a small red box (in which is a tie-pin and a paperclip)").
- showAdditionalInfo: flag - do we want to show additional information (such as 'providing light') after the name of items in this list. The default value is true.
- showWornInfo: flag - do we want to show '(being worn)' after the names of items in this list that are being worn. By default we use the value of showAdditionalInfo. Note that the text ' (being worn)' can be customized by using a CustomMessages object to change the text of the being worn BMsg.
- showMovedToInfo: flag - do we want to show ' (by the whatever)' after the names of items whose movedTo property is whatever. By default we do (in most games this will only rarely occur).
- showSubListing: flag - do we want to show the contents of items listed in inventory (in parentheses after the name, e.g. 'a bag (in which is a blue ball)'). The default value on ItemLister is true but lookLister, descContentsLister, lookContentsLister, lookInLister and simpleAttachmentLister all take the value of this property from gameMain.useParentheticalListing.
Types of Lister
The adv3Lite defines a number of different types of Lister for various specific purposes. The definition of these listers is split between lister.t (which defines the base functionality) and english.t (which defines the language-specific aspects). The lister modifications in english.t generally do not use the BMsg()/DMsg() mechanism to generate text, since such indirection would be superfluous in a part of the library which is in any case language-specific and where modification may be more simply achieved by overriding the relevant methods such as showList(), showListPrefix(), showListSuffix() and showListEmpty().
The specific kinds of Lister defined in the library are:
- lookLister: displays a list of top-level miscellaneous objects (i.e. those without a specialDesc or initSpecialDesc) in a room description.
- lookContentsLister: displays a list of the contents of objects in a room description (e.g. if the room description mentions a box, the lookContentsLister would be used to list the contents of that box in the room description).
- remoteRoomContentsLister: the default Lister for listing miscellaneous objects in a remote location.
- inventoryLister: displays an inventory listing (i.e. a list of objects carried by the player character, or perhaps by an NPC).
- inventoryListerTall: displays an inventory listing in TALL (i.e. columnar) format.
- wornLister: displays a list of items being worn (usually by the player character).
- descContentsLister: displays a list of the miscellaneous contents (items without a specialDesc or initSpecialDesc) of an object that is being examined. Note that if the item being examined is an openable container this lister also reports if it's open , even if it doesn't contain anything, unless the item's openStatusReportable property is nil. To have this report begin "It's open and contains.." rather that "The whatever is open and contains..." set
openStatusReportabletoUsePronoun - openingContentsLister: displays the contents of an openable container when it is first opened. Note that in the English-specific part of the library (english.t) this lister is also responsible for reporting that the item has been opened when it is empty.
- lookInLister: lists the contents of an object in response to LOOK IN/UNDER/BEHIND.
- simpleAttachmentLister: lists the items attached to a SimpleAttachable.
- plugAttachableLister: lists the items plugged into a PlugAttachable.
- CustomRoomLister: A lister that can be readily customized to tailor the text before and after the list of miscellaneous items in a room description. For further details see the chapter on Room Descriptions.
- subLister: The subLister is used by other listers such as inventoryLister and wornLister to show the contents of listed items in parentheses (e.g. '(in which is a pen, a pencil and a piece of paper)'). The depth of nesting is limited by the subLister's maxNestingDepth property (which defaults to 1). Note that this lister is defined in english.t, not lister.t.
- remoteSubContentsLister: the default Lister for listing the miscellaneous contents of objects in a remote location.
- finishOptionsLister: The lister used to list the options from which the player can choose when the game comes to an end. Note that this is defined in english.t, not lister.t, and that it descends from Lister but not ItemLister.
Note that all but the last of the above descend from ItemLister.
Customizing Listers
Customizing a lister is basically a matter of overriding one or more of the methods and properties described above. Most typically you might want to change the way a list is introduced or concluded by overriding showListPrefix() or showListSuffix(). You might want to do this globally, in which case you can just modify the lister concerned, for example:
modify lookLister
showListPrefix(lst, pl, parent)
{
"Lying on the floor <<if pl>>are<<else>>is<<end>> ";
}
showListSuffix(lst, pl, parent)
{
". ";
}
;
This would then affect the way in which miscellaneous items were listed in every room in the game. More typically, though, we might want to affect the way things are listed in a particular room or on a particular object, in which case we can override the appropriate property of Thing to point to a custom Lister object:
- roomContentsLister: The contents lister to use for listing this room's miscellaneous contents. By default we use the standard lookLister but this can be overridden to use a CustomRoomLister (say) to provide just about any wording we like.
- remoteContentsLister: The contents lister to use for listing this room's miscellaneous contents when viewed from a remote location. By default we use remoteRoomContentsLister.
- examineLister: The lister to use to list this item's contents when it's examined. By default we use descContentsLister.
- myOpeningContentsLister: The lister to use when listing this item's contents when it's opened. By default we use the openingContentsLister.
- myLookInLister: The lister to use when listing the objects inside this item in response to a LOOK IN or SEARCH command. By default we use the lookInLister.
- myLookUnderLister: The lister to use when listing the objects under this item in response to a LOOK UNDER command. By default we use the lookInLister (note this is not an error; the default lookInLister uses whatever preposition is appropriate to the item).
- myLookBehindLister: The lister to use when listing the objects behind this item in response to a LOOK BEHIND command. By default we use the lookInLister (note this is not an error; the default lookInLister uses whatever preposition is appropriate to the item).
- myInventoryLister: The lister to use when listing this object's inventory. By default we use inventoryLister.
- myWornLister: The lister to use when listing what this object is wearing. By default we use wornLister.
- attachmentLister: The lister to use when listing what is attached to a SimpleAttachable. By default we use simpleAttachmentLister. On a PlugAttachable we instead use plugAttachableLister.
To make use of these properties, you'd typically define your own custom lister based on the appropriate one from the library, and then attach it to the relevant property, probably as an anonymous object (unless you wanted to use the same custom lister on a number of different objects). For example, to customize the lister used when opening or looking in a box you could do something like this:
+ largeBox: OpenableContainer 'large box'
myOpeningContentsLister: openingContentsLister
{
showListPrefix(lst, pl, parent)
{
"On ripping open the box {i} discover{s/ed} ";
}
}
myLookInLister: lookInLister
{
showListPrefix(lst, pl, parent)
{
"Lurking inside the box <<if pl>>are<<else>>is<<end>> ";
}
}
;
For more information on customizing the way items are listed in room descriptions, see the chapters on Room Descriptions and (for remote rooms) SenseRegions.
Ordering and Grouping
We can control the order in which miscellaneous items are liste by using their listOrder property (the list will be sorted in ascending order of listOrder). Beyond that, just about any custom ordering of miscellaneous item can be achieved by grouping them, e.g., to group keys or writing implements or coins together in any room description or inventory listing.
To group miscellaneous items in a list you need to:
- Define an object of class ListGroup (or rather, one of its subclasses)
- Assign the listWith property of all the items you want listed in this group to this ListGroup object
This should hopefully become much clearer with an example. Suppose we add a pencil, a ruler, and a bottle of ink to the miscellaneous items in the room. We might then see a listing like:
You see a chair, a ruler, a pencil, a bucket of water, a bottle of ink, a pen, and a piece of paper here.
But since the ruler, the pencil, the ink, the pen and the paper are all writing implements of a sort, we might prefer them all to be listed together. The first step is to create an appropriate ListGroup:
writingMaterials: ListGroupSorted ;
Then we assign each of the objects in question to this ListGroup, e.g.:
+ ruler: Thing 'ruler' 'ruler'
listWith = [writingMaterials]
;
Note that the listWith property takes a list of ListGroups, since the same item can belong to more than one ListGroup. We'll say a bit more about multiple ListGroups in this property later; for now we'll keep it simple and stick to one. Once we've defined our writingMaterials ListGroup and assigned all the appropriate objects to it in this way, the list will display something like:
Note that we made our writingMaterials list group a ListGroupSorted and not just a ListGroup. This is because the base ListGroup class doesn't actually do much when it comes to listing its members; it's nearly always more useful, therefore, to use a subclass of ListGroup, ListGroupSorted being perhaps the most general-purpose of those subclasses. As its name suggest, ListGroupSorted can also sort the items it lists. Suppose, for example, we not only wanted to group the writing implements together, but wanted them to appear in the order: pen, pencil, ink, paper, ruler. We can do this by first defining a compareGroupItems() method on our writingMaterials list group, which could make use of a the groupOrder property on the writing implements themselves:
writingMaterials: ListGroupSorted
/*
* Return an integer > 0 if the first item sorts after the second item;
* Return an integer < 0 if the second item sorts after the first item;
* Return zero if the two items are at the same sorting order.
*/
compareGroupItems (a, b) { return a.groupOrder - b.groupOrder; }
;
+ pencil: Thing 'pencil' 'pencil'
listWith = [writingMaterials]
groupOrder = 20
;
+ ink: Thing 'bottle/ink' 'bottle of ink'
listWith = [writingMaterials]
groupOrder = 30
;
/* etc. */
Note that we didn't need to override compareGroupItems (a, b) in this instance, since sorting in ascending order of groupOrder is what it already does by default, but this illustrates how we could implement some other custom listing order. Note also that by default Things take their groupOrder from their listOrder, so if we were content for the two to remain the same we wouldn't need to override groupOrder.
With our modified writingMaterials list group we'll now get a list like:
You see a chair; a pen, a pencil, a bottle of ink, a piece of paper, and a ruler; and a bucket of water here.
ListGroupSorted has a couple of subclasses we can also use for particular purposes. We could use ListGroupPrefixSuffix to explicitly introduce the list of writing implements as "some writing implements":
writingMaterials: ListGroupPrefixSuffix
showGroupPrefix(pov, lst)
{
"some writing materials: ";
}
;
This would result in:
You see a chair; some writing materials: a pen, a pencil, a bottle of ink, a piece of paper, and a ruler; and a bucket of water here.
Alternatively we could have overridden groupPrefix to 'writing implements' and then we would have got:
You see a chair; five writing implements: a pen, a pencil, a bottle of ink, a piece of paper, and a ruler; and a bucket of water here.
Note that we could likewise use the groupSuffix property to append text to the end of the list of writing materials. In a more complex situation we could instead override the methods showGroupPrefix(pov, lst) and showGroupSuffix(pov, lst), which by default just display the groupPrefix and groupSuffix properties; in a more complex case we might want to use these methods to vary the text shown according to the value of the pov parameter (normally the actor doing the looking) or the lst parameter (the list of items about to be listed).
We can use ListGroupParen to give a general description of the items (e.g. "five writing implements" followed by the actual items listed in parentheses:
writingMaterials: ListGroupParen
showGroupCountName(lst)
{
"<<spellInt(lst.length)>> writing implements";
}
;
Or, more simply but equivalently:
writingMaterials: ListGroupParen
pluralName = 'writing implements'
;
Either of which results in:
You see a chair, five writing implements (a pen, a pencil, a bottle of ink, a piece of paper, and a ruler), and a bucket of water here.
Note that if we hadn't overridden either showGroupCountName() or pluralName it would simply have used the count , so the list would have been shown as "five (a pen, a pencil, ...)" intead of "five writing implements (a pen, a pencil, ...)"
Finally, we could use ListGroupCustom (a subclass of ListGroup, but not of ListGroupSorted) just to give a summary name for the pen, pencil etc. without listing the individual items at all:
writingMaterials: ListGroupCustom
showGroupMsg (lst) { "some writing materials"; }
;
Resulting it:
You see a chair, some writing materials, and a bucket of water here.
This is probably not a good idea unless the individual items answer to the vocabulary "writing materials" (perhaps as a plural), since otherwise the player won't be able to refer to them!
In summary, the ListGroup classes that are most useful, together with the properties you'll commonly want to override on them, are:
- ListGroupSorted; compareGroupItems(a, b) defines the sorting order (by default this is ascending order of the items' groupOrder property which by default is the same as their listOrder)
- ListGroupPrefixSuffix; groupPrefix and groupSuffix define the text used to introduce and conclude the list
- ListGroupParen; pluralName defines the collective name for the items about to be listed in parentheses (e.g. "writing implements")
- ListGroupCustom; showGroupMsg(lst) defines the text used to summarize the list of items (in place of the items themselves), e.g. "some writing materials"
There's a couple more points to bear in mind about these ListGroups. First, you may be wondering what happens if only one member of a group is present; it would be ugly - or at least needlessly pedantic - to see a list like "You see one writing implement (a pen) here". This is taken care of by the minGroupSize property on the ListGroup object. By default the value of this property is two, which means that the ListGroup will only be used to group its members in a list if at least two of them are present. This is the behaviour we'd normally want, but we can always change this property to something other than two if we need to.
The other main point is the complication introduced above: the listWith property of an object contains a list of ListGroup objects, and that list can contain more than one member, meaning that an object can be grouped in more than one way. When multiple groups are specified here, the ListGroup an item will be listed on will depend on.
- If the ListGroups involved have different priorities, the ListGrouop with the highest priority will be used (provided the difference is a big enough number, where 'big enough' means larger than the number of items in any one group)
- Otherwise, the ListGroup that would contain the largest number of items is used (in the event of a tie, the choice will be arbitrary)
Finally, we can control where a given ListGroup appears in a list of items by setting its own listOrder property. So for example, if we wanted our group of writing materials to appear after everything else, we could give our writingMaterials ListGroup a listOrder of 200 (assuming this was greater than the listOrder of everything else).
Generating Lists
So far we have been looking at techniques for formatting lists once we have a list of objects to format. The other part of the process is generating the list of objects in the first place. This happens in various places in the library. It will not be appropriate to go into all of them in too much detail here. Instead we shall simply give an overview and refer the interested reader to the relevant parts of the Library Reference Manual for the nitty-gritty low-level detail.
The top-level method used to generate lists of objects for a room description is the Thing method listContents(). This has to generate not one but three lists: the list of items with specialDescs to show before the miscellaneous items, the list of miscellaneous items, and the list of items with specialDescs to show after the list of miscellaneous items. For items with specialDescs the method then just runs through each of the two lists showing the appropriate specialDesc or initSpecialDesc, having sorted the list in order of the specialDescOrder property. The list of miscellaneous items is displayed in between the two lists of items with specialDescs using the lister passed as a parameter to listContents(lister), which defaults to roomContentsLister (itself a property of Thing, which in turn defaults to lookLister, the actual Lister object employed, as noted above).
The listContents() method is further complicated by a number of other tasks it needs to perform, such as listing the contents of the player character's immediate location first, if the player character is in a nested room, ensuring that hidden items are excluded from the list, listing the contents of any remote locations if the senseRegion.t module is present, noting that any items listed have been seen by the player character, and listing any visible contents of any of the items just listed that want their contents listed (for which it calls the listSubContentsOf() method). From this it can be seen that listContents() has a complex and specialized task to perform, and is used only to generate the lists of items to be shown in a room description.
The Thing method listSubcontentsOf(contList, lister) is, as just mentioned, used to list the subcontents of the top-level contents of a room, but is used elsewhere in the library as well and could conceivably be called directly from game code. The contList parameter is supplied as a list of items (or a singleton item) whose contents are in turn to be listed. The optional lister parameter is the Lister object to be used to list the subcontents; if this parameter is not explicitly supplied it defaults to examineLister (which is in turn a property of Thing which defaults to the descContentsLister Lister objected, as noted above). The listSubContentsOf() method sorts the list passed to it in listOrder order and then excludes certain items from the list (those that are hidden, carried by an actor, impossible to see inside, or empty). It then goes through each item than remains in the list and divides it contents into objects with specialDescs to be listed before miscellaneous contents, the miscellaneous contents, and objects with specialDescs to be listed after the miscellaneous contents, excluding all objects that are hidden or already mentioned. Those items with specialDescs thus have their specialDescs shown, while any miscellaneous items are listed using the lister that was passed as the second parameter to listSubcontentsOf(). Finally, the contents of all these items are then listed with a recursive call to listSubContentsOf(). This may sound quite complicated, but the effect is to produce a complete list of everything that should be listed arranged in the correct order with specialDescs uses as appropriate and the listing carried on to the depth of nesting needed to list every visible object within the containment hierarchy of the list originally passed to listSubContentsOf(). In short, listSubcontentsOf() is the method to use to list the complete contents of anything (or a list of anything) that isn't a room.
In addition to being called from listContents(), listSubcontentsOf() is the method used to list the relevant contents of objects in response to an EXAMINE command (via the examineStatus() method), an OPEN command (when opening a container reveals its contents) and a LOOK IN, LOOK UNDER or LOOK BEHIND command. In the case of these last four commands, listSubcontentsOf() is called from the action() section of the relevant dobjFor() block.
Analogous methods are used to generate lists of items visible in remote locations when showing a room description. In particular the Thing method listRemoteContents(lst, lister, pov) overridden in senseRegion.t is used to list a set of items in lst from the point of view of an actor pov using the Lister lister. In essence it does much the same job for a remote location as listContents() does for the player character's immediate location, but is only relevant when two or more locations are connected by sight within a SenseRegion.
Lists are also generated and/or used at various other places in the library, such as in the definition of the Inventory action, the processOptions(lst) function used to display a list of options at the end of the game, a couple of places in actor.t that show lists of suggested topics, the examineStatus() method of the SimpleAttachable class and its subclasses (to list the attached objects), and the showFullScore() method of the libScore object (used to show a list of achievements). Interested readers are referred to the Library Reference Manual for details.
List-Related Functions
The language-specific part of the library (in english.t) defines a number of list-related functions that are used by the library and are also available to user code:
- makeListStr(objList, nameProp = &aName, conjunction = 'and'): Takes a list of objects supplied in objList and return a formatted list in a single quoted string, having first sorted the items in objList in the order of their listOrder property. If the nameProp parameter is supplied, we'll use that property for the name of every item in the list; otherwise we use the aName property by default. By default the last two items in the list are separated by 'and', but we can choose a different conjunction by supplying the conjunction parameter.
- orList(lst): Returns a printable list of strings separated by "or" conjunctions (e.g. if lst is supplied as ['a duck', 'a rabbit', 'a partridge'] the function will return 'a duck, a rabbit and a partridge'). Note that the lst parameter should be supplied as a list of strings, not object, and the function returns a single string.
- andList(lst): this is similar to orList(), except that it returns a printable list of strings separated by "and" conjunctions.
- genList(lst, conj): the general list constructor used by orList() and andList(); lst is the list of strings to be formatted into a single list, and conj is the oonjunction to use, supplied as a single-quoted string.
- mergeDuplicates(lst): service function used by genList(); takes a list of strings of the form ['a book', 'a cat', 'a book'] and merges the duplicate items to return a list of the form ['two books', 'a cat'].
- makeCountedPlural(str, num): service function used by mergeDuplicates(); takes the string representation of a name (str) and a number (num) and returns a string with the number spelled out and the name pluralised, e.g. makeCountPlural('a cat', 3) -> 'three cats'. Also deals with the more complex cases such as makeCountedPlural('(taking the coin)'), 3) -> '(taking three coins)'; i.e. the function substitutes the number for the first occurrence of an article, if there is one.
- stripArticle(txt): service function used by makeCountedPlural(); removes any definite or indefinite article that occurs at the beginning of txt, and returns the resultant string in lower case.
